/*
 * The org.opensourcephysics.media.core package defines the Open Source Physics
 * media framework for working with video and other media.
 *
 * Copyright (c) 2004 Douglas Brown and Wolfgang Christian.
 *
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This software is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston MA 02111-1307 USA or view the license online at
 * http://www.gnu.org/copyleft/gpl.html
 *
 * For additional information and documentation on Open Source Physics, please
 * see <http://www.opensourcephysics.org/>.
 */
package org.opensourcephysics.media.core;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;

import javax.swing.*;

import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.tools.ResourceLoader;
import org.opensourcephysics.display.*;

/**
 * This is a Filter that subtracts a baseline image from the source image.
 *
 * @author Douglas Brown
 * @version 1.0
 */
public class BaselineFilter extends Filter {

	// instance fields
	private BufferedImage source, input, output, baseline;
	private int[] pixels, baselinePixels;
	private int w, h;
	private Graphics2D gIn;
	private Inspector inspector;
	private String imagePath;
	private JFileChooser chooser;
	private JButton loadButton;
	private JButton captureButton;

	/**
	 * Constructs a default BaselineFilter.
	 */
	public BaselineFilter() {
  	hasInspector = true;
	}

	/**
	 * Captures the current video frame to use as baseline image.
	 */
	public void capture() {
		if (vidPanel == null || vidPanel.getVideo() == null) return;
		setBaselineImage(vidPanel.getVideo().getImage());
	}

	/**
	 * Loads a baseline image from the specified path.
	 *
	 * @param path the image path
	 */
	public void load(String path) {
		BufferedImage image = ResourceLoader.getBufferedImage(path);
		if (image != null) {
			imagePath = path;
			setBaselineImage(image);
		}
		else {
			JOptionPane.showMessageDialog(vidPanel, "\"" + path + "\" " + //$NON-NLS-1$ //$NON-NLS-2$
							MediaRes.getString("Filter.Baseline.Dialog.NotImage.Message"), //$NON-NLS-1$
							MediaRes.getString("Filter.Baseline.Dialog.NotImage.Title"), //$NON-NLS-1$
							JOptionPane.INFORMATION_MESSAGE);
		}
	}

	/**
	 * Loads an image with a file chooser.
	 */
	public void load() {
		if (chooser == null) chooser = new JFileChooser(new File(
						OSPRuntime.chooserDir));
		chooser.setDialogTitle("Open"); //$NON-NLS-1$
		int result = chooser.showOpenDialog(null);
		if (result == JFileChooser.APPROVE_OPTION) {
			File file = chooser.getSelectedFile();
			load(file.getAbsolutePath());
		}
	}

	/**
	 * Sets the baseline image.
	 *
	 * @param image the image
	 */
	public void setBaselineImage(BufferedImage image) {
		baseline = image;
		if (image != null) {
			int wi = image.getWidth();
			int ht = image.getHeight();
			if (wi >= w && ht >= h) {
				image.getRaster().getDataElements(0, 0, w, h, baselinePixels);
			}
			else {
				JOptionPane.showMessageDialog(
						vidPanel,
						MediaRes.getString("Filter.Baseline.Dialog.SmallImage.Message1") + " (" + wi + "x" + ht + ") " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
						MediaRes.getString("Filter.Baseline.Dialog.SmallImage.Message2") + " (" + w + "x" + h + ").", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
						MediaRes.getString("Filter.Baseline.Dialog.SmallImage.Title"), //$NON-NLS-1$
						JOptionPane.INFORMATION_MESSAGE);
			}
		}
		support.firePropertyChange("baseline", null, null); //$NON-NLS-1$
	}

	/**
	 * Gets the baseline image being subtracted.
	 *
	 * @return the image
	 */
	public BufferedImage getBaselineImage() {
		return baseline;
	}

	/**
	 * Applies the filter to a source image and returns the result.
	 *
	 * @param sourceImage the source image
	 * @return the filtered image
	 */
	public BufferedImage getFilteredImage(BufferedImage sourceImage) {
		if (!isEnabled()) return sourceImage;
		if (sourceImage != source) initialize(sourceImage);
		if (sourceImage != input) gIn.drawImage(source, 0, 0, null);
		subtractBaseline();
		return output;
	}

	/**
	 * Implements abstract Filter method.
	 *
	 * @return the inspector
	 */
	public JDialog getInspector() {
  	if (inspector == null) inspector = new Inspector();
  	if (inspector.isModal() && vidPanel != null) {
  		Frame f = JOptionPane.getFrameForComponent(vidPanel);
    	if (frame != f) {
    		frame = f;
    		if (inspector != null)
    			inspector.setVisible(false);
      	inspector = new Inspector();
    	}
    }
    inspector.initialize();
		return inspector;
	}

	/**
	 * Clears this filter
	 */
	public void clear() {
		setBaselineImage(null);
	}

	/**
	 * Refreshes this filter's GUI
	 */
	public void refresh() {
		super.refresh();
		loadButton.setText(MediaRes.getString("Filter.Baseline.Button.Load")); //$NON-NLS-1$
		captureButton.setText(MediaRes.getString("Filter.Baseline.Button.Capture")); //$NON-NLS-1$
		captureButton.setText(MediaRes.getString("Filter.Baseline.Button.Capture")); //$NON-NLS-1$
		if (inspector != null) {
			inspector.setTitle(MediaRes.getString("Filter.Baseline.Title")); //$NON-NLS-1$
			inspector.pack();
		}
		loadButton.setEnabled(isEnabled());
		captureButton.setEnabled(isEnabled());
	}

	// _____________________________ private methods _______________________

	/**
	 * Creates new input, output and baseline images.
	 *
	 * @param image a new source image
	 */
	private void initialize(BufferedImage image) {
		source = image;
		w = source.getWidth();
		h = source.getHeight();
		pixels = new int[w * h];
		baselinePixels = new int[w * h];
		output = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
		if (source.getType() == BufferedImage.TYPE_INT_RGB) input = source;
		else {
			input = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
			gIn = input.createGraphics();
		}
	}

	/**
	 * Sets the output to an image-subtracted version of the input.
	 */
	private void subtractBaseline() {
		input.getRaster().getDataElements(0, 0, w, h, pixels);
		if (baseline != null) {
			int pixel, base, r, g, b;
			for (int i = 0; i < pixels.length; i++) {
				pixel = pixels[i];
				base = baselinePixels[i];
				r = (pixel >> 16) & 0xff; // red
				r = r - ((base >> 16) & 0xff);
				r = Math.max(r, 0);
				g = (pixel >> 8) & 0xff; // green
				g = g - ((base >> 8) & 0xff);
				g = Math.max(g, 0);
				b = pixel & 0xff; // blue
				b = b - (base & 0xff);
				b = Math.max(b, 0);
				pixels[i] = (r << 16) | (g << 8) | b;
			}
		}
		output.getRaster().setDataElements(0, 0, w, h, pixels);
	}

	/**
	 * Inner Inspector class to control filter parameters
	 */
	private class Inspector extends JDialog {

		/**
		 * Constructs the Inspector.
		 */
		public Inspector() {
      super(frame, !(frame instanceof org.opensourcephysics.display.OSPFrame));
			setTitle(MediaRes.getString("Filter.Baseline.Title")); //$NON-NLS-1$
			setResizable(false);
			createGUI();
			refresh();
			pack();
      // center on screen
      Rectangle rect = getBounds();
      Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
      int x = (dim.width - rect.width) / 2;
      int y = (dim.height - rect.height) / 2;
      setLocation(x, y);
		}

		/**
		 * Creates the visible components.
		 */
		void createGUI() {
			// create buttons
			loadButton = new JButton();
			loadButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					load();
				}
			});
			// create buttons
			captureButton = new JButton();
			captureButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					capture();
				}
			});
			// add components to content pane
			JPanel buttonbar = new JPanel(new FlowLayout());
			setContentPane(buttonbar);
			buttonbar.add(ableButton);
			buttonbar.add(loadButton);
			buttonbar.add(captureButton);
			buttonbar.add(clearButton);
			buttonbar.add(closeButton);
		}

		/**
		 * Initializes this inspector
		 */
		void initialize() {
			refresh();
		}

	}

	/**
	 * Returns an XML.ObjectLoader to save and load filter data.
	 *
	 * @return the object loader
	 */
	public static XML.ObjectLoader getLoader() {
		return new Loader();
	}

	/**
	 * A class to save and load filter data.
	 */
	static class Loader implements XML.ObjectLoader {

		/**
		 * Saves data to an XMLControl.
		 *
		 * @param control the control to save to
		 * @param obj the filter to save
		 */
		public void saveObject(XMLControl control, Object obj) {
			BaselineFilter filter = (BaselineFilter)obj;
			if (filter.imagePath != null) control.setValue(
							"imagepath", filter.imagePath); //$NON-NLS-1$
      if (filter.frame != null && filter.inspector != null &&
      				filter.inspector.isVisible()) {
        int x = filter.inspector.getLocation().x - filter.frame.getLocation().x;
        int y = filter.inspector.getLocation().y - filter.frame.getLocation().y;
        control.setValue("inspector_x", x); //$NON-NLS-1$
        control.setValue("inspector_y", y); //$NON-NLS-1$
      }
		}

		/**
		 * Creates a new filter.
		 *
		 * @param control the control
		 * @return the new filter
		 */
		public Object createObject(XMLControl control) {
			return new BaselineFilter();
		}

		/**
		 * Loads a filter with data from an XMLControl.
		 *
		 * @param control the control
		 * @param obj the filter
		 * @return the loaded object
		 */
		public Object loadObject(XMLControl control, Object obj) {
			final BaselineFilter filter = (BaselineFilter)obj;
			if (control.getPropertyNames().contains("imagepath")) //$NON-NLS-1$
			filter.load(control.getString("imagepath")); //$NON-NLS-1$
      filter.inspectorX = control.getInt("inspector_x"); //$NON-NLS-1$
      filter.inspectorY = control.getInt("inspector_y"); //$NON-NLS-1$
			return obj;
		}
	}
}
